#ifndef PLUGINTERFACE_H
#define PLUGINTERFACE_H

#include "DynamicLibrary.h"

#include <string>
#include <vector>
#include <iostream>
#include <map>

//Platform-specific file extensions
//NOTE: Undefined platforms will cause a compile-error
#if defined(PF_PLATFORM_MAC)
	static std::string dynamicLibraryPrefix("lib");
	static std::string dynamicLibraryExtension(".dylib");
#elif defined(PF_PLATFORM_LINUX)
	static std::string dynamicLibraryPrefix("lib");
	static std::string dynamicLibraryExtension(".so");
#elif defined(PF_PLATFORM_WINDOWS)
	static std::string dynamicLibraryPrefix("");
	static std::string dynamicLibraryExtension(".dll");
#else
    #error No (known) platform was specified in the compiler options.     \
	   You may have forgotten to add the compiler flag -DPF_PLATFORM_XXX, \
	   or you may be attempting to compile on an unsupported platform.
	static std::string dynamicLibraryPrefix( "" );
	static std::string dynamicLibraryExtension( "" );
#endif

static std::string pluginDir("plugins/");

// The following ifdef block is the standard way of creating macros which make
// exporting from a DLL simpler. All files within this DLL are compiled with the
// CPP_PLUGIN_EXPORTS symbol defined on the command line. this symbol should not
// be defined on any project that uses this DLL. This way any other project
// whose source files include this file see CPP_PLUGIN_API functions as being
// imported from a DLL, whereas this DLL sees symbols defined with this macro as
// being exported.
#ifdef WIN32
	#ifdef BUILDING_PLUGIN
		#define PLUGIN_API __declspec(dllexport)
	#else
		#define PLUGIN_API __declspec(dllimport)
	#endif
#else
	#define PLUGIN_API
#endif

//TODO: Consider how this class could be made to handle templated interfaces and
//      plugins. One could put a template definition before using the below
//      macro, but the calling code may need adjustment.

//Use this at the bottom of your plugin header file to make the plugin usable.
#define DECLARE_PLUGIN(Class, Interface) extern "C"                           \
                                         /*PLUGIN_API*/ __declspec(dllexport) \
                                         Interface* instantiatePlugin()       \
									     { return new Class(); }
//Deprecated
#define DEFINE_PLUGIN(Class, Interface) extern "C"                            \
                                         /*PLUGIN_API*/ __declspec(dllexport) \
                                         Interface* instantiatePlugin()       \
									     { return new Class(); }

/* TODO: Consider using self-registration method in order to avoid having to
determine interface type. See how the current method holds up first though. */

template<class T_Interface>
class Pluginterface
{
	//Our plugin-class instantiation function-pointer type (signature of
	// DEFINE_PLUGIN function)
	typedef T_Interface* (InstantiationFunc)();
	
	//A reference-counting plugin class with a pointer the corresponding dynamic
	// library.
	class Plugin {
		DynamicLibrary*    m_pLibrary;
		InstantiationFunc* m_fpInstantiate;
		int                m_iRefCount;
		//These cannot be used
		Plugin();
		Plugin(Plugin& p);
	public:
		Plugin(DynamicLibrary* pLibrary, InstantiationFunc* fpInstantiate)
		 : m_pLibrary(pLibrary), m_fpInstantiate(fpInstantiate), m_iRefCount(1) 
		{
			
		}
		~Plugin()
		{
			if ( m_iRefCount != 0 )
				std::cerr << "Error in Plugin: Deconstructing Plugin when \
				  reference count = " << m_iRefCount << " != 0." << std::endl;
		}
		void         incRefCount()     { ++m_iRefCount; }
		int          decRefCount()
		{
			if ( 0 == m_iRefCount-- ) {
				delete m_pLibrary;
				m_pLibrary = 0;
			}
			return m_iRefCount;
		}
		int          getRefCount()     { return m_iRefCount; }
		T_Interface* createInstance()  { return m_fpInstantiate(); }
	};
	
	typedef std::map<std::string, Plugin*> PluginMap;
	//NOTE: Need typename here because of this     \/. Otherwise it doesn't know
	//      that it is a typename.
	typedef typename std::map<std::string, Plugin*>::iterator PluginMapIterator;
	
	//TODO: Consider making this static
	PluginMap m_pLoadedPluginsMap;
	
public:
	
	Pluginterface()     {}
	~Pluginterface()
	{
		if ( !m_pLoadedPluginsMap.empty() ) {
			std::cerr << "Error in Pluginterface: Deconstructing Pluginterface while the following plugins were apparently still in use:" << std::endl;
			for ( PluginMapIterator itPlugin = m_pLoadedPluginsMap.begin(); itPlugin != m_pLoadedPluginsMap.end(); ++itPlugin) {
				std::cerr << "\t`" << itPlugin->first << "' with reference count = " << itPlugin->second->getRefCount() << std::endl;
				delete itPlugin->second;
			}
			m_pLoadedPluginsMap.clear();
			std::cerr << "The above plugins have now been deleted from memory. You can only hope the program doesn't still need them." << std::endl;
		}
	}
	
	void loadPlugin(std::string sName)
	{
		
		//Check if library has already been loaded
		PluginMapIterator itPlugin = m_pLoadedPluginsMap.find(sName);
		if ( itPlugin != m_pLoadedPluginsMap.end() ) {
			Plugin* pPlugin = itPlugin->second;
			pPlugin->incRefCount();
			return;
		}
		
		DynamicLibrary*    pLibrary      = NULL;
		InstantiationFunc* fpInstantiate = NULL;
		
		//Load the dynamic library
		std::string sError;
		pLibrary = DynamicLibrary::load(pluginDir + dynamicLibraryPrefix + sName + dynamicLibraryExtension, sError);
		if ( !pLibrary ) {
			std::cerr << "Error in Pluginterface: " << sError << std::endl;
			return;
		}
		
		//Find the plugin's instantiation function
		fpInstantiate = (InstantiationFunc*)pLibrary->getSymbol("instantiatePlugin");
		if ( !fpInstantiate ) {
			std::cerr << "Error in Pluginterface: loadPlugin( \"" << sName << "\" ) failed. This plugin does \
								not have a valid instantiatePlugin function. Plugin cannot be loaded." << std::endl;
			delete pLibrary;
			return;
		}
		
		//Add the new plugin to the map
		m_pLoadedPluginsMap[sName] = new Plugin(pLibrary, fpInstantiate);
	}
	
	T_Interface* createInstance(std::string sName)
	{
		
		//TODO: This could either 1) get the function pointer from the named plugin and create an instance, *or*
		//      2) it could get the function pointer from a list of class names that is added-to when plugins are loaded.
		//      Method 2 will likely require some bigger changes to data structures and loading and things. Need to decide if want\need to allow many classes within one plugin first.
		
		//Check if library has already been loaded
		PluginMapIterator itPlugin = m_pLoadedPluginsMap.find(sName);
		if ( itPlugin == m_pLoadedPluginsMap.end() ) {
			std::cerr << "Error in Pluginterface: createInstance( \"" << sName << "\" ) failed: No class with this name was found. Make sure the correct plugin has been loaded first." << std::endl;
			return NULL;
		}
		Plugin* pPlugin = itPlugin->second;
		
		//Call the plugin's instantiate function to create an instance of its class
		T_Interface* pInstance = pPlugin->createInstance();
		if ( !pInstance ) {
			std::cerr << "Error in Pluginterface: createInstance( \"" << sName << "\" ) failed: Instantiation of class failed." << std::endl;
			return NULL;
		}
		
		return pInstance;
		
	}
	
	//Shortcut function to save a line of code in a common usage pattern
	T_Interface* loadAndInstantiatePlugin(std::string sName)
	{
		loadPlugin(sName);
		return createInstance(sName);
	}
	
	void unloadPlugin(std::string sName)
	{
		PluginMapIterator itPlugin = m_pLoadedPluginsMap.find(sName);
		if ( itPlugin == m_pLoadedPluginsMap.end() ) {
			std::cerr << "Error in Pluginterface: Request to unload plugin named `" << sName << "' when it doesn't seem to have been loaded." << std::endl;
			return;
		}
		Plugin* pPlugin = itPlugin->second;
		if ( 0 == pPlugin->decRefCount() ) {
			delete pPlugin;
			m_pLoadedPluginsMap.erase(itPlugin);
		}
	}
	
	std::vector<std::string> getList()
	{
		
		/* TODO: Find all files in the plugins directory with the correct extension and check if they are valid plugins to this Pluginterface. */
		/*       This will require cross-platform directory-handling code. I think there is some in boost. */
		
	}

};

#endif //PLUGINTERFACE_H
